﻿using System;
using biz.ritter.javapi.util;
using biz.ritter.javapi.lang;
using biz.ritter.javapi.io;

namespace Nofs.Net.AnnotationDriver
{
    public abstract class ClassList
    {
        //@SuppressWarnings("unchecked")
        public static Map<String, Set<Class>> findClasses(ClassLoader classLoader) //throws ClassNotFoundException 
        {
            return findClasses(classLoader, null, null, null);
        }

        /**
         * Searches the classpath for all classes matching a specified search
         * criteria, returning them in a map keyed with the interfaces they
         * implement or null if they have no interfaces. The search criteria can be
         * specified via interface, package and jar name filter arguments
         * <p>
         *
         * @param classLoader
         *            The classloader whose classpath will be traversed
         * @param interfaceFilter
         *            A Set of fully qualified interface names to search for or null
         *            to return classes implementing all interfaces
         * @param packageFilter
         *            A Set of fully qualified package names to search for or or
         *            null to return classes in all packages
         * @param jarFilter
         *            A Set of jar file names to search for or null to return
         *            classes from all jars
         * @return A Map of a Set of Classes keyed to their interface names
         *
         * @//throws ClassNotFoundException
         *             if the current thread's classloader cannot load a requested
         *             class for any reason
         */
        //@SuppressWarnings("unchecked")
        public static Map<String, Set<Class>> findClasses(
                ClassLoader classLoader,
                Set<String> interfaceFilter, Set<String> packageFilter,
                Set<String> jarFilter) //throws ClassNotFoundException 
        {

            Map<String, Set<Class>> classTable = new HashMap<String, Set<Class>>();

            foreach (Type item in classLoader.GetExportedTypes())
            {
                if (!item.IsInterface)
                {
                    LinkedList<Type> classInterfaces = new LinkedList<Type>();

                    foreach (Type t in item.GetInterfaces())
                    {
                        classInterfaces.add(t);
                    }
                    classInterfaces.add(item.GetType());
                    
                    String interfaceName = null;
                    int len = classInterfaces.size();
                    foreach(Type t in classInterfaces)
                    {
                        interfaceName = t.Name;
                        // was this interface requested?
                        if (interfaceName.Contains("`") ||
                                (interfaceFilter != null && !interfaceFilter.contains(interfaceName)))
                        {
                            continue;
                        }
                       

                        // is this interface already in the map?
                        if (classTable.containsKey(interfaceName))
                        {
                            // if so then just add this class to the end of the
                            // list of classes implementing this interface
                            classTable.get(interfaceName).add(item.getClass());
                        }
                        else
                        {
                            // else create a new list initialised with our first
                            // class and put the list into the map
                            Set<Class> allClasses = new HashSet<Class>();
                            allClasses.add(item.getClass());
                            classTable.put(interfaceName, allClasses);
                        }
                    }
                }
            }

            return classTable;
        } // end method

        /**
         * Recursively lists a directory while generating relative paths. This is a
         * helper function for findClasses. Note: Uses a StringBuffer to avoid the
         * excessive overhead of multiple String concatentation
         *
         * @param dirListing
         *            A list variable for storing the directory listing as a list of
         *            Strings
         * @param dir
         *            A File for the directory to be listed
         * @param relativePath
         *            A StringBuffer used for building the relative paths
         */
        private static void recursivelyListDir(List<String> dirListing, File dir,
                StringBuffer relativePath)
        {
            int prevLen; // used to undo append operations to the StringBuffer

            // if the dir is really a directory
            if (dir.isDirectory())
            {
                // get a list of the files in this directory
                File[] files = dir.listFiles();
                // for each file in the present dir
                for (int i = 0; i < files.Length; i++)
                {
                    // store our original relative path string length
                    prevLen = relativePath.length();
                    // call this function recursively with file list from present
                    // dir and relateveto appended with present dir
                    recursivelyListDir(dirListing, files[i], relativePath.append(
                            prevLen == 0 ? "" : "/").append(files[i].getName()));
                    // delete subdirectory previously appended to our relative path
                    relativePath.delete(prevLen, relativePath.length());
                }
            }
            else
            {
                // this dir is a file; append it to the relativeto path and add it
                // to the directory listing
                dirListing.add(relativePath.toString());
            }
        }
    }

}
